#pragma once
#include "canvas.h"
#include "scan_line.h"
#include "fixed_point_helper.h"
#include "texture_software.h"
#include <iostream>

// we need:
//  - vertex transformations (partially done)
//  - fixed point triangle filler (DONE)
//
//  - sub-pixel correctness (DONE)
//  - texturing sub-texel / sub-pixel correct (DONE)
//  - Z-Buffer
//  - mip-mapping

//TODO:
//  - Sutherland-Hodgeman clipping against Z = 0

// Extra
//  - Trilinear, anisotropic filtering
//  - Guard bands
//  - Multi-texturing, alpha mapping

// couple of things:
//  - we might want to output to a SoA type of screen buffer instead of a regular one

namespace krryn{
namespace system_imp{
#pragma region Z-Buffer

	template<typename _Type>
	class z_buffer{
	public:
		z_buffer(int a_Width, int a_Height) : m_Width(a_Width), m_Height(a_Height){
			m_Buffer = new _Type[a_Width * a_Height];
			m_Scanline = 0;
			clear();
		}

		~z_buffer(){
			delete[] m_Buffer;
		}

		forceinline bool operator()(const int a_X, _Type a_Z){
			/// Temporary
			if(m_Scanline < m_Buffer) return false;
			if(a_X < 0) return false;
			if(a_X >= m_Width) return false;
			if(m_Scanline >= m_Buffer + (m_Width * m_Height)) return false;

			if(a_Z >= m_Scanline[a_X]){
				m_Scanline[a_X] = a_Z;
				return true;
			}

			return false;
		}

		forceinline void set_scanline(int a_Y){
			m_Scanline = m_Buffer + a_Y * m_Width;
		}

		forceinline void next_scanline(){
			m_Scanline += m_Width;
		}

		void clear(){
			int l_Size = m_Width * m_Height;
			for(int i = 0; i < l_Size; i++){
				m_Buffer[i] = fp_values<_Type>::zero();
			}
			m_Scanline = 0;
		}
	private:
		int m_Width;
		int m_Height;
		_Type *m_Scanline;
		_Type *m_Buffer;
	};

#pragma endregion

#pragma region Pixel plotting routines
	template<typename _Type>
	forceinline view::pixel_t make_pixel(const _Type a_R, const _Type a_G, const _Type a_B){
		return
			  ((view::pixel_t)(a_R) << 16)
			+ ((view::pixel_t)(a_G) << 8)
			+ ((view::pixel_t)(a_B));
	}

	template<>
	forceinline view::pixel_t make_pixel<int>(const int a_R, const int a_G, const int a_B){
		return
			  ((((a_R) >> SHIFT_FACTOR) << 16) & 0xFF0000)
			+ ((((a_G) >> SHIFT_FACTOR) << 8)  & 0x00FF00)
			+ (((a_B)  >> SHIFT_FACTOR)        & 0x0000FF);
	}
#pragma endregion

#pragma region Gouraud shading
	template<typename _Description, typename _Type>
	struct shade_color{
	public:
		enum{
			R = 4,
			G = 5,
			B = 6
		};

		void set_canvas(view::canvas *a_Canvas) {}

		forceinline view::pixel_t operator()(const scan_line<_Description, _Type> &a_Convert){
			return make_pixel(
				a_Convert.m_Values[R] * 255, 
				a_Convert.m_Values[G] * 255, 
				a_Convert.m_Values[B] * 255
			);
		}
	};
#pragma endregion

#pragma region Depth shader
	template<typename _Description, typename _Type>
	struct shade_depth{
	public:
		enum{
			Z = 2
		};

		void set_canvas(view::canvas *a_Canvas) {}

		forceinline view::pixel_t operator()(const scan_line<_Description, _Type> &a_Convert){
			return make_pixel(
				a_Convert.m_Values[Z] * 255, 
				a_Convert.m_Values[Z] * 255, 
				a_Convert.m_Values[Z] * 255
			);
		}
	};
#pragma endregion

#pragma region Linear texture filler
	template<typename _Description, typename _Type>
	struct shade_texture_linear{
	public:
		enum{
			U = 4,
			V = 5
		};

		view::canvas *m_Canvas;

		shade_texture_linear() : m_Canvas(0) {}

		void set_canvas(view::canvas *a_Canvas) { m_Canvas = a_Canvas; }

		forceinline view::pixel_t operator()(const scan_line<_Description, _Type> &a_Convert){
			int l_X = to_int<_Type>(a_Convert.m_Values[U] * m_Canvas->get_width());
			int l_Y = to_int<_Type>(a_Convert.m_Values[V] * m_Canvas->get_height());

			l_X &= (m_Canvas->get_width() - 1);
			l_Y &= (m_Canvas->get_height() - 1);

			return *(m_Canvas->get_buffer() + l_Y * m_Canvas->get_pitch() + l_X);
		}
	};
#pragma endregion

#pragma region Bilinear texture filler
	template<typename _Description, typename _Type>
	struct shade_texture_bilinear{
	public:
		enum{
			U = 4,
			V = 5
		};

		view::canvas *m_Canvas;

		shade_texture_bilinear() : m_Canvas(0) {}

		void set_canvas(view::canvas *a_Canvas) { m_Canvas = a_Canvas; }

		forceinline view::pixel_t operator()(const scan_line<_Description, _Type> &a_Convert){
			_Type l_U = mul_int<_Type>(a_Convert.m_Values[U], m_Canvas->get_width());
			_Type l_V = mul_int<_Type>(a_Convert.m_Values[V], m_Canvas->get_height());

			int l_X  =  to_int<_Type>(l_U) & (m_Canvas->get_width() - 1);
			int l_Y  =  to_int<_Type>(l_V) & (m_Canvas->get_height() - 1);;

			int l_X1 = (to_int<_Type>(l_U) + 1) & (m_Canvas->get_width() - 1);
			int l_Y1 = (to_int<_Type>(l_V) + 1) & (m_Canvas->get_height() - 1);

			_Type l_Du = fraction<_Type>(l_U);
			_Type l_Dv = fraction<_Type>(l_V);

			_Type l_DuInv = fp_values<_Type>::one() - l_Du;
			_Type l_DvInv = fp_values<_Type>::one() - l_Dv;

			_Type l_W1 = mul<_Type>(l_DuInv, l_DvInv);
			_Type l_W2 = mul<_Type>(l_DuInv, l_Dv);
			_Type l_W3 = mul<_Type>(l_Du,    l_DvInv);
			_Type l_W4 = mul<_Type>(l_Du,    l_Dv);

			view::pixel_t l_Pixel1 = get(l_X , l_Y); // top-left
			view::pixel_t l_Pixel2 = get(l_X , l_Y1); // bottom-left
			view::pixel_t l_Pixel3 = get(l_X1, l_Y); // top-right
			view::pixel_t l_Pixel4 = get(l_X1, l_Y1); // bottom-right

			view::pixel_t l_Red1   = (l_Pixel1 & 0xFF0000) >> 16;
			view::pixel_t l_Red2   = (l_Pixel2 & 0xFF0000) >> 16;
			view::pixel_t l_Red3   = (l_Pixel3 & 0xFF0000) >> 16;
			view::pixel_t l_Red4   = (l_Pixel4 & 0xFF0000) >> 16;

			view::pixel_t l_Green1 = (l_Pixel1 & 0x00FF00) >> 8;
			view::pixel_t l_Green2 = (l_Pixel2 & 0x00FF00) >> 8;
			view::pixel_t l_Green3 = (l_Pixel3 & 0x00FF00) >> 8;
			view::pixel_t l_Green4 = (l_Pixel4 & 0x00FF00) >> 8;

			view::pixel_t l_Blue1  = l_Pixel1 & 0x0000FF;
			view::pixel_t l_Blue2  = l_Pixel2 & 0x0000FF;
			view::pixel_t l_Blue3  = l_Pixel3 & 0x0000FF;
			view::pixel_t l_Blue4  = l_Pixel4 & 0x0000FF;

			_Type l_Red   = l_W1 * l_Red1   + l_W2 * l_Red2   + l_W3 * l_Red3   + l_W4 * l_Red4;
			_Type l_Green = l_W1 * l_Green1 + l_W2 * l_Green2 + l_W3 * l_Green3 + l_W4 * l_Green4;
			_Type l_Blue  = l_W1 * l_Blue1  + l_W2 * l_Blue2  + l_W3 * l_Blue3  + l_W4 * l_Blue4;

			return make_pixel<_Type>(l_Red, l_Green, l_Blue);
		}

		forceinline view::pixel_t get(int a_X, int a_Y){
			return m_Canvas->get_buffer()[a_Y * m_Canvas->get_pitch() + a_X];
		}
	};
#pragma endregion

#pragma region Generic interpolator
	template<typename _Description, typename _Type>
	class interpolator{
	public:
		enum{
			X = 0,
			Y = 1,
			Z = 2
		};

		template<typename _Shader, typename _DepthBuffer>
		forceinline void shade(
			_Shader &a_Shader,
			_DepthBuffer &a_Depth,
			view::pixel_t *a_Scanline,
			scan_line<_Description, _Type> a_Left,  // must be copies
			scan_line<_Description, _Type> a_Right) // must be copies
		{
			int l_Start  = fp_ceil<_Type>(a_Left.m_Values[X]);
			int l_Finish = fp_ceil<_Type>(a_Right.m_Values[X]);

			if(l_Start >= l_Finish || l_Start < 0) return;

			pixel_t *l_Current = a_Scanline + l_Start;
			pixel_t *l_End     = a_Scanline + l_Finish;

			_Type l_Width = a_Right.m_Values[X] - a_Left.m_Values[X];

			_Type l_InvDeltaX  = inverse<_Type>(l_Width);
			_Type l_Correction = sub_int<_Type>(l_Start, a_Left.m_Values[X]);
			
			a_Left.recalculate_slopes(a_Right, l_InvDeltaX);
			a_Left.sub_pixel(l_Correction);

			// use sub-pixel correct X value
			int l_X = to_int<_Type>(a_Left.m_Values[X]);

			while(l_Current <= l_End){
				if(a_Depth(l_X, a_Left.m_Values[Z])){
					*l_Current = a_Shader(a_Left);
				}

				l_X++;
				l_Current++;
				a_Left.interpolate();
			}
		}
	};
#pragma endregion

	template<typename _Vertex, typename _Type = float>
	class triangle_renderer_software{
	public:
		typedef _Vertex vertex_type;
		typedef typename _Vertex::description_type description_type;

		enum{
			X = 0,
			Y,
			Z,
			W,
			U,
			V
		};


		triangle_renderer_software() : m_Canvas(0), m_Depth(640, 480) {}

		void set_canvas(view::canvas *a_Canvas) {
			m_Canvas = a_Canvas; 
			m_Depth.clear();
		}

		void set_texture(system_imp::texture_software *a_Texture){
			m_Texture = a_Texture;
		}

		// we're not using the outline-table drawing method, but instead,
		// this rasterizer draws two triangles. It splits up the regular
		// triangle into two parts separated by a perfect horizontal line 
		// at the kink. (Called scan convert).
		//
		// major axis
		//   |
		//   |    v1
		//   |    *
		//   |   ****   <-- top triangle
		//   |> *******
		//     ********** v2 <-- split happens here
		//    *******
		//   ***** <-- bottom triangle
		//  ***
		// * v3
		//
		void draw(const _Type *a_V1, const _Type *a_V2, const _Type *a_V3){
			// sort triangle by y-coordinate
			// Notice that the vertices are in screen 
			// coordinates and thus are v1 <= v2 <= v3
			if(a_V1[Y] > a_V2[Y]) std::swap(a_V1, a_V2);
			if(a_V1[Y] > a_V3[Y]) std::swap(a_V1, a_V3);
			if(a_V2[Y] > a_V3[Y]) std::swap(a_V2, a_V3);

			/// can't do 1.f/n here because of a potential division by zero
			_Type l_InvDeltaY[] = {
				a_V3[Y] - a_V1[Y],
				a_V2[Y] - a_V1[Y],
				a_V3[Y] - a_V2[Y]
			};

			// we can early-out if the major axis' length is 0
			if(l_InvDeltaY[0] == 0) return;

			// as a side effect of the mip-map calculation, we determine the
			// size of the triangle on screen, if it's to small to draw we 
			// don't accept it
			update_mip_map_level(a_V1, a_V2, a_V3);

			// check the orientation of the triangle. Reverse the scans if 
			// needed (done later on in draw_subtriangle)
			m_Scan[0].m_IsLeftSide = check_is_left_side(a_V1, a_V2, a_V3);
			m_Scan[1].m_IsLeftSide = !m_Scan[0].m_IsLeftSide;

			// initialize major axis
			m_Scan[0].init_edge(a_V3, a_V1, inverse<_Type>(l_InvDeltaY[0]));

			// handle the top triangle
			if(l_InvDeltaY[1] != 0){
				m_Scan[1].init_edge(a_V2, a_V1, inverse<_Type>(l_InvDeltaY[1]));
				draw_subtriangle(a_V1, a_V2, m_Scan[0], m_Scan[1]);
			}

			// handle the bottom triangle
			if(l_InvDeltaY[2] != 0){
				// advance forcefully due to possible inconsitencies in the 
				// interpolation code (rounding errors for example)
				if(l_InvDeltaY[1] != 0){
					m_Scan[0].skip(
						a_V1, 
						a_V2[Y] - a_V1[Y]
					);
				}

				m_Scan[1].init_edge(a_V3, a_V2, inverse<_Type>(l_InvDeltaY[2]));
				draw_subtriangle(a_V2, a_V3, m_Scan[0], m_Scan[1]);
			}
		}

	private:
		forceinline bool update_mip_map_level(
			const _Type *a_V1, const _Type *a_V2, const _Type *a_V3)
		{
			// notice, normally the area of a triangle is .5 det(a, b, c, d)
			// but we're not dividing areas taken by the triangle by 2
			// because we can do that at the end of the entire calculation,
			// saving one division \o/

			// det(a, b, c, d) = a*d - b*c
			_Type l_ScreenArea = det_2x2(
				a_V2[X] - a_V1[X],
				a_V2[Y] - a_V1[Y],

				a_V3[X] - a_V1[X], 
				a_V3[Y] - a_V1[Y]
			);

			/*_Type l_ScreenArea = math::abs(
				 (a_V2[X]*a_V1[Y]-a_V1[X]*a_V2[Y])
				+(a_V3[X]*a_V2[Y]-a_V2[X]*a_V3[Y])
				+(a_V1[X]*a_V3[Y]-a_V3[X]*a_V1[Y]));*/

			int l_MipMapLevel = 1;

			//if(l_ScreenArea != fp_values<_Type>::zero())
			{
				// calculate the area taken by the highest mip-map level
				int l_Width  = m_Texture->get_canvas_at(0)->get_width();
				int l_Height = m_Texture->get_canvas_at(0)->get_height();

				/*_Type l_TextureArea = math::abs(
					 (a_V2[U]*a_V1[V]-a_V1[U]*a_V2[V])
					+(a_V3[U]*a_V2[V]-a_V2[U]*a_V3[V])
					+(a_V1[U]*a_V3[V]-a_V3[U]*a_V1[V]));*/

				
				_Type l_TextureArea = det_2x2(
					mul_int<_Type>(a_V2[U] - a_V1[U], l_Width),
					mul_int<_Type>(a_V2[V] - a_V1[V], l_Height),
					mul_int<_Type>(a_V3[U] - a_V1[U], l_Width),
					mul_int<_Type>(a_V3[V] - a_V1[V], l_Height)
				);

			/*	l_MipMapLevel = log2<_Type>(
					div<_Type>(l_TextureArea, l_ScreenArea)
				) / 2;*/

				_Type l_TotalArea = l_TextureArea * l_TextureArea +
					l_ScreenArea * l_ScreenArea;

//				l_MipMapLevel = mip_map<_Type>(l_TotalArea);
			}

			m_Shader.set_canvas(m_Texture->get_canvas_at(0));

			return true;
		}

		template<typename _Description>
		forceinline void draw_subtriangle(
			const _Type *a_Top,
			const _Type *a_Bottom,
			scan_line<_Description, _Type> &a_Scan0, 
			scan_line<_Description, _Type> &a_Scan1)
		{
			// these ints are regular integers (eg. not fixed point or anything)
			int l_Start = fp_ceil<_Type>(a_Top[Y]);
			int l_End   = fp_ceil<_Type>(a_Bottom[Y]) - 1;

			// account for sub-pixel accuracy
			_Type l_Correction = sub_int<_Type>(l_Start, a_Top[Y]);
			a_Scan0.sub_pixel(l_Correction);
			a_Scan1.sub_pixel(l_Correction);

			// revert scanning direction if needed
			scan_line<_Description, _Type> *l_Scan[2];
			l_Scan[a_Scan0.m_IsLeftSide] = &a_Scan0;
			l_Scan[a_Scan1.m_IsLeftSide] = &a_Scan1;

			pixel_t *l_Scanline = m_Canvas->get_buffer() 
				+ l_Start * m_Canvas->get_pitch();

			pixel_t *l_ScanlineEnd = m_Canvas->get_buffer()  
				+ l_End * m_Canvas->get_pitch();

			m_Depth.set_scanline(l_Start);

			while(l_Scanline <= l_ScanlineEnd){
				m_Interpolator.shade(
					m_Shader, m_Depth, 
					l_Scanline, *l_Scan[0], *l_Scan[1]
				);

				// interpolate vertex data over this scanline
				a_Scan0.interpolate();
				a_Scan1.interpolate();

				l_Scanline += m_Canvas->get_pitch();
				m_Depth.next_scanline();
			}
		}

		forceinline bool check_is_left_side(
			const _Type *a_V1, const _Type *a_V2, const _Type *a_V3)
		{
			int l_Temp[] = {
				  to_int<_Type>(a_V1[X] - a_V3[X]) 
				* to_int<_Type>(a_V2[Y] - a_V1[Y]),

				  to_int<_Type>(a_V1[Y] - a_V3[Y])
				* to_int<_Type>(a_V2[X] - a_V1[X])
			};

			return l_Temp[0] - l_Temp[1] <= 0;
		}

		scan_line<description_type, _Type> m_Scan[2];

		// we might want a way to select the shader at runtime
		interpolator<description_type, _Type> m_Interpolator;
		shade_texture_linear<description_type, _Type> m_Shader;
		system_imp::texture_software *m_Texture;
		z_buffer<_Type> m_Depth;
		view::canvas *m_Canvas;
	};
}
}	